import logging
from collections import defaultdict

import networkx as nx
from py2neo import Graph
import heapq
import numpy as np
import matplotlib.pyplot as plt

# 设置中文字体（需确保系统中存在该字体）
plt.rcParams['font.sans-serif'] = ['SimHei']  # Windows 系统常用黑体
plt.rcParams['axes.unicode_minus'] = False    # 解决负号显示问题
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger("EnhancedCollaborativeLearning")


class EnhancedCollaborativeLearning:
    def __init__(self, neo4j_uri, user, password, alpha=0.85, feedback_strength=0.1, dynamic_threshold=0.008):
        self.graphdb = Graph(neo4j_uri, auth=(user, password))
        self.alpha = alpha
        self.feedback_strength = feedback_strength
        self.dynamic_threshold = dynamic_threshold
        self.G = nx.DiGraph()
        self.node_attrs = {}
        self.node_info = {}
        self.pr_history = defaultdict(list)
        self.metrics = {
            'cost': [],
            'path_length': [],
            'pr_std': [],
            'pr_max_diff': []
        }

    def _plot_metrics(self):
        """可视化关键指标"""
        plt.figure(figsize=(14, 8))
        plt.subplot(1, 3, 1)
        plt.plot(self.metrics['cost'], 'o-')
        plt.title('路径成本迭代变化')
        plt.xlabel('迭代轮次')
        plt.ylabel('成本')
        plt.grid(True)

        plt.subplot(1, 3, 2)
        plt.plot(self.metrics['path_length'], 's-')
        plt.title('路径长度迭代变化')
        plt.xlabel('迭代轮次')
        plt.ylabel('节点数')
        plt.grid(True)

        plt.subplot(1, 3, 3)
        plt.plot(self.metrics['pr_std'], '^-')
        plt.title('权威值标准差变化')
        plt.xlabel('迭代轮次')
        plt.ylabel('标准差')
        plt.grid(True)

        plt.tight_layout()
        plt.savefig('collaborative_metrics.png', dpi=300)
        plt.close()

    def _generate_metric_report(self):
        """生成指标分析报告"""
        if not self.metrics['cost']:
            logger.warning("无有效指标数据，跳过报告生成")
            return

        initial_cost = self.metrics['cost'][0]
        final_cost = self.metrics['cost'][-1]
        cost_reduction = (initial_cost - final_cost) / initial_cost if initial_cost else 0.0
        is_self_loop = (initial_cost == 0 and final_cost == 0)
        cost_stability = np.std(self.metrics['cost'][-5:]) if len(self.metrics['cost']) >= 5 else 0.0
        pr_std_mean = np.mean(self.metrics['pr_std'])
        pr_max_diff = self.metrics['pr_max_diff'][-1]

        report = {
            '最终成本': final_cost,
            '成本下降率': cost_reduction,
            '路径收敛稳定性': cost_stability,
            '权威值集中度': pr_std_mean,
            '权威值最大差异': pr_max_diff
        }
        conclusions = []
        if is_self_loop:
            conclusions.append("自循环路径无需优化，直接返回最优解")
        else:
            if report['成本下降率'] > 0.9:
                conclusions.append("算法优化显著：成本下降率超过 90%，路径搜索效率极高。")
            elif report['成本下降率'] > 0.5:
                conclusions.append("算法有效但可优化：成本下降率 50%~90%，建议调整反馈强度或迭代次数。")
            else:
                conclusions.append("优化效果不足：成本下降率低于 50%，需检查数据连通性或参数设置。")
            if report['路径收敛稳定性'] < 1.0:
                conclusions.append("路径收敛稳定：最后5次迭代成本波动小（标准差 <1.0），结果可靠。")
            else:
                conclusions.append("收敛不稳定：成本波动较大，建议增加迭代次数或降低学习率。")
        if report['权威值集中度'] < 0.1:
            conclusions.append("权威值分布均匀：节点权威值差异小，系统处于平衡状态。")
        else:
            conclusions.append("权威值差异显著：存在核心知识点聚集现象，符合教学预期。")
        if report['权威值最大差异'] < self.dynamic_threshold:
            conclusions.append("权威值区分不足：建议提高反馈强度或调整参数设置。")

        pr_changes = {nid: abs(hist[-1] - hist[0]) for nid, hist in self.pr_history.items() if len(hist) >= 2}
        top_nodes = sorted(pr_changes.items(), key=lambda x: x[1], reverse=True)[:5]
        print("\n=== TOP5关键节点权威值变化 ===")
        for nid, delta in top_nodes:
            detail = self.get_node_details(nid)
            init_pr = self.pr_history[nid][0]
            final_pr = self.pr_history[nid][-1]
            rel = (delta / init_pr) if init_pr else 0
            print(
                f"节点{nid} ({detail['title']}): 初始 {init_pr:.4f} → 最终 {final_pr:.4f}, 绝对 {delta:.4f}, 相对 {rel:.2%}")

        print("\n=== 指标分析报告 ===")
        for k, v in report.items(): print(f"{k}: {v:.4f}")
        print("\n=== 分析结论 ===")
        for c in conclusions: print(c)

        with open('metric_report.txt', 'w') as f:
            f.write("=== Collaborative Learning Metrics ===\n")
            for k, v in report.items(): f.write(f"{k}: {v}\n")
            f.write("\n=== Conclusions ===\n")
            f.write("\n".join(conclusions))

    def _record_metrics(self, path, cost):
        """记录单次迭代指标"""
        # 基础指标记录
        self.metrics['cost'].append(cost)
        self.metrics['path_length'].append(len(path))

        # 权威值数据准备
        pr_values = [attrs.get('edu_pr', 0.0) for attrs in self.node_attrs.values() if attrs]

        # 标准差计算保护
        if len(pr_values) >= 2:
            self.metrics['pr_std'].append(np.std(pr_values))
        else:
            self.metrics['pr_std'].append(0.0)
            logger.warning("权威值不足2个，标准差设为0")

        # 最大差异计算保护
        if pr_values:
            self.metrics['pr_max_diff'].append(max(pr_values) - min(pr_values))
        else:
            self.metrics['pr_max_diff'].append(0.0)
            logger.warning("无有效权威值，最大差异设为0")

    def load_graph(self):
        """动态加载图数据"""
        self.G = nx.DiGraph()
        self.node_attrs = defaultdict(dict)
        self.node_info = {}

        q_nodes = """
           MATCH (n) 
           WHERE n:KnowledgePoint OR n:SubKnowledgePoint OR n:Chapter
           RETURN 
               id(n) as nid,
               labels(n)[0] as label,
               coalesce(n.title, '未命名节点') as title,
               coalesce(n.chapter_id, 0) as chapter_id,
               coalesce(n.edu_pagerank, 0.0) as edu_pr
           """
        for rec in self.graphdb.run(q_nodes):
            nid = rec['nid']
            self.G.add_node(nid)
            self.node_info[nid] = {
                'label': rec['label'],
                'title': rec['title'],
                'chapter_id': rec['chapter_id']
            }
            self.node_attrs[nid] = {'edu_pr': max(rec['edu_pr'], 0.03)}  # 初始值下限设为 0.03

        q_edges = """
           MATCH (a)-[r]->(b)
           WHERE (a:KnowledgePoint OR a:SubKnowledgePoint OR a:Chapter)
             AND (b:KnowledgePoint OR b:SubKnowledgePoint OR b:Chapter)
           RETURN 
               id(a) as src, 
               id(b) as tgt, 
               coalesce(r.weight, 1.0) as weight
           """
        for rec in self.graphdb.run(q_edges):
            self.G.add_edge(rec['src'], rec['tgt'], weight=rec['weight'] or 1.0)
        logger.info(f"加载节点数: {self.G.number_of_nodes()}, 边数: {self.G.number_of_edges()}")

    def get_node_details(self, node_id):
        """获取节点详细信息"""
        if node_id not in self.node_info:
            return None
        return {
            'id': node_id,
            'label': self.node_info[node_id]['label'],
            'title': self.node_info[node_id]['title'],
            'chapter_id': self.node_info[node_id]['chapter_id']
        }

    def update_pagerank(self):
        """PageRank计算"""
        pr = nx.pagerank(self.G, alpha=self.alpha, weight='weight')
        total = sum(pr.values())
        for nid in pr:
            self.node_attrs[nid]['edu_pr'] = pr[nid] / total
            self.graphdb.run(
                "MATCH (n:KnowledgePoint) WHERE id(n)=$nid SET n.edu_pagerank = $pr",
                nid=nid, pr=pr[nid]
            )
        logger.info("PageRank权威值更新完成")

    def a_star_search(self, start, goal):
        if start == goal:
            return [start], 0  # 明确自循环路径成本为0
        """优化A*算法"""
        if start not in self.G or goal not in self.G:
            logger.error("起始或目标节点不存在")
            return None, float('inf')

        open_heap = []
        heapq.heappush(open_heap, (0, start))
        came_from = {}
        g_score = {n: float('inf') for n in self.G.nodes}
        g_score[start] = 0

        while open_heap:
            current_cost, current = heapq.heappop(open_heap)

            if current == goal:
                return self._reconstruct_path(came_from, goal), g_score[goal]

            for neighbor in self.G.successors(current):
                edge_weight = self.G[current][neighbor]['weight']
                pr_weight = 1 / (self.node_attrs[neighbor]['edu_pr'] + 1e-6)
                tentative_score = g_score[current] + edge_weight * pr_weight

                if tentative_score < g_score[neighbor]:
                    came_from[neighbor] = current
                    g_score[neighbor] = tentative_score
                    h_score = np.linalg.norm([current - goal])
                    heapq.heappush(open_heap, (tentative_score + h_score, neighbor))

        return None, float('inf')

    def validate_path(self, path):
        """检查路径中相邻节点是否实际连通"""
        for i in range(len(path) - 1):
            if not self.G.has_edge(path[i], path[i + 1]):
                logger.warning(f"非法路径段：{path[i]} -> {path[i + 1]}")
                return False
        return True

    def evaluate_and_adjust(self, path):
        """评估调整逻辑增强权威值区分度"""
        iteration = len(self.metrics['cost'])
        dynamic_feedback = max(0.2, 0.5 * (0.8 ** iteration))

        # 动态衰减率调整
        decay_rate = 0.5 if iteration < 5 else 0.7
        for u, v in self.G.edges():
            if u in path or v in path:
                self.G[u][v]['weight'] *= decay_rate

        self.update_pagerank()
        logger.info("完成权重修正")

    def collaborative_iteration(self, start, goal, max_iters=10):
        """动态迭代框架（含模拟退火机制）"""
        self.pr_history = defaultdict(list)  # 确保每次迭代独立初始化

        if start == goal:
            best_path, min_cost = self._handle_self_loop(start)
            if best_path is not None:
                self._record_metrics(best_path, min_cost)
                self._finalize_iteration()
            return best_path, min_cost

        self.load_graph()
        best_path, min_cost = None, float('inf')
        unstable_count = 0
        original_alpha = self.alpha

        for iteration in range(max_iters):
            self.alpha = self._dynamic_alpha(iteration, max_iters, original_alpha)
            logger.info(f"=== 迭代第 {iteration + 1} 轮 (alpha={self.alpha:.2f}) ===")
            path, cost = self.a_star_search(start, goal)

            # 增强路径验证逻辑：允许空路径触发终止，但仅更新有效路径
            if not path:
                logger.warning("无法找到有效路径，终止迭代")
                break
            if self.validate_path(path) and cost < min_cost:
                best_path, min_cost = path.copy(), cost

            # 记录权威值历史
            for nid in self.G.nodes():
                self.pr_history[nid].append(self.node_attrs[nid].get('edu_pr', 0))

            # 动态延长迭代机制
            unstable_count = self._check_convergence(cost, unstable_count)
            if unstable_count >= 3:
                logger.info("检测到持续不稳定，延长迭代次数至40")
                max_iters = 40

            self.evaluate_and_adjust(path)
            self._record_metrics(path, cost)

        self._finalize_iteration()
        return best_path, min_cost

    def _dynamic_alpha(self, current_iter, max_iters, base_alpha):
        """模拟退火参数调整"""
        progress = current_iter / max_iters
        # 初期高alpha加速收敛，后期降低提升稳定性
        return max(0.6, base_alpha * (1 - progress * 0.3))

    def _check_convergence(self, cost, unstable_count):
        """收敛稳定性检测"""
        if len(self.metrics['cost']) >= 4:
            last_3 = self.metrics['cost'][-3:]
            if max(last_3) - min(last_3) > 1.0:
                return unstable_count + 1
        return 0

    def _reconstruct_path(self, came_from, current):
        """路径回溯"""
        path = [current]
        while current in came_from:
            current = came_from[current]
            path.append(current)
        return path[::-1]

    def _handle_self_loop(self, node_id):
        """自循环路径处理"""
        self.load_graph()
        self._record_metrics([node_id], 0)  # 确保自循环路径的权威值被记录
        self.update_pagerank()  # 强制更新权威值
        self._record_metrics([node_id], 0)
        for nid in self.G.nodes():
            self.pr_history[nid].append(self.node_attrs[nid].get('edu_pr', 0))
        if node_id not in self.G.nodes:
            logger.error("自循环节点不存在于图谱中")
            return None, float('inf')
        print(f"\n路径搜索：{self.get_node_details(node_id)['label']} [自循环路径]")
        return [node_id], 0

    def _validate_and_update(self, path, cost, best_path, min_cost):
        """路径验证与更新逻辑"""
        if not path:
            logger.warning("无法找到有效路径，终止迭代")
            return False
        if cost < min_cost and self.validate_path(path):
            best_path, min_cost = path, cost
        return True

    def _finalize_iteration(self):
        """迭代收尾工作"""
        self._plot_metrics()
        self._generate_metric_report()
        # 重置临时存储
        self.pr_history = {}

# 修改后的主程序部分
if __name__ == "__main__":
    # 定义多组测试用例（不同起止节点组合）
    test_cases = [
        {"start": 11, "goal": 74, "desc": "常规知识点路径"},
        {"start": 14, "goal": 592, "desc": "跨章节长路径"},
        {"start": 34, "goal": 34, "desc": "自循环节点测试"},
        {"start": 19, "goal": 576, "desc": "高权威值节点路径"}
    ]

    final_results = []

    # 每次测试独立初始化系统
    for case in test_cases:
        # 每次测试创建新实例
        system = EnhancedCollaborativeLearning(
            neo4j_uri="bolt://localhost:7687",
            user="neo4j",
            password="123456789",
            alpha=0.8,  # 调整初始alpha
            feedback_strength=0.5  # 增强反馈强度
        )
        system.load_graph()

        # 节点存在性检查
        start_exists = case["start"] in system.G.nodes
        goal_exists = case["goal"] in system.G.nodes
        if not (start_exists and goal_exists):
            print(f"\n测试用例 {case['desc']} 无效：")
            if not start_exists:
                print(f"  起始节点 {case['start']} 不存在于知识图谱")
            if not goal_exists:
                print(f"  目标节点 {case['goal']} 不存在于知识图谱")
            continue

        print(f"\n=== 测试用例 [{case['desc']}] 起止节点 ({case['start']}->{case['goal']}) ===")
        best_path, final_cost = system.collaborative_iteration(
            start=case["start"],
            goal=case["goal"],
            max_iters=30
        )
        if best_path is None or len(best_path) == 0:
            logger.warning(f"测试用例 {case['desc']} 未找到有效路径")
            continue
        # 记录结果
        final_results.append({
            "desc": case["desc"],
            "path": best_path,
            "cost": final_cost,
            "metrics": system.metrics.copy()
        })

    # 跨测试用例分析
    print("\n=== 跨测试用例综合分析 ===")
    valid_cases = [
        res for res in final_results
        if res["path"] is not None
           and len(res["path"]) > 1  # 排除自循环路径
           and res["metrics"]['cost'][0] > 0  # 排除零初始成本
    ]

    analysis = {
        "成功用例数": len(valid_cases),
        "平均成本下降率": np.mean([
            (case["metrics"]['cost'][0] - case["metrics"]['cost'][-1]) / case["metrics"]['cost'][0]
            for case in valid_cases
        ]) if valid_cases else 0,
        "权威值集中趋势": np.mean([
            case["metrics"]['pr_std'][-1]
            for case in valid_cases
            if len(case["metrics"]['pr_std']) >= 1
        ]) if valid_cases else 0
    }

    print(f"成功路径比例: {analysis['成功用例数']}/{len(test_cases)}")
    print(f"平均成本优化率: {analysis['平均成本下降率']:.2%}")
    print(f"权威值最终集中度: {analysis['权威值集中趋势']:.4f}")